/*******************************************************************************
 * Copyright(c) 2023 Levetop Semiconductor Co.,Led. All rights reserved.
 * @file     w25qxx.c
 * @author   UartTFT Application Team
 * @version  V0.0.1
 * @date     2023-01-01
 * @brief
 ********************************************************************************/

#include "w25qxx.h"
#include "delay.h"
#include "qspi.h"
#include "ssi_reg.h"
#include "dmac_drv.h"
#include "ssi_drv.h"
#include "dma.h"

uint8_t Flash_Type = 0; // Flash_Type = 0, NorFlash 24bits address
						// Flash_Type = 1, NorFlash 32bits address
						// Flash_Type = 2, Nand Flash
uint16_t Flash_ID = 0;
uint16_t Nand_page = 0; // Nand Flash current page,used to determine whether to switch page

void Flash_SS(uint8_t bitval)
{
	if (bitval == 0)
		Flash_SS_RESET;
	else if (bitval == 1)
		Flash_SS_SET;
}

//----------------------------------------------status register---------------------------------------
/********************************************************************************
* Function Name	: W25QXX_ReadSR
* Description  	: Read the value of the register.
* Input        	: - SRx:Register address
					  - 3 status registers
					  - W25X_READ_SR1: address 0x05
						     BIT   7   6   5   4   3   2   1   0
                                  SPR  RV TB  BP2  BP1 BP0 WEL BUSY
					  - W25X_READ_SR2: address 0x35
					  - W25X_READ_SR3: address 0x15
* Output       	: None
* Return       	: retVal
*********************************************************************************/
uint8_t W25QXX_ReadSR(uint8_t SRx)
{
	uint8_t retVal = 0;

	SSI_TypeDef *SSIx = SSI1;

	SSIx->SSI_DR = SRx;
	SSIx->SSI_DR = 0xff;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		retVal = SSIx->SSI_DR;
	}

	return retVal;
}

/********************************************************************************
* Function Name	: W25QXX_WriteSR
* Description  	: Write the value of the register.
* Input        	: - SRx:Register address
					 - 3 status registers
					    - W25X_WRITE_SR1: address 0x01
					    - W25X_WRITE_SR2: address 0x31
					    - W25X_WRITE_SR3: address 0x11
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_WriteSR(uint8_t SRx, uint8_t val)
{
	volatile uint8_t retVal;

	SSI_TypeDef *SSIx = SSI1;

	W25QXX_Write_Enable(); // enable
	// W25QXX_Write_EnableVolatile();

	SSIx->SSI_DR = SRx;
	SSIx->SSI_DR = val;

	//__asm("nop"); __asm("nop"); __asm("nop");
	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		retVal = SSIx->SSI_DR;
	}

	W25QXX_Wait_Busy();
}


/********************************************************************************
* Function Name	: W25QXX_Wait_Busy
* Description  	: Wait for BUSY bit to clear.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_Wait_Busy(void)
{
	while ((W25QXX_ReadSR(W25X_READ_SR1) & 0x01))
		;
}

/********************************************************************************
* Function Name	: W25QXX_Write_Enable
* Description  	: Write Enable(set WEL to 1).
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_Write_Enable(void)
{
	volatile uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	SSI1_STD_Init(SSI1);

	if ((W25QXX_ReadSR(W25X_READ_SR1) & 0x02) == 0)
	{
		SSIx->SSI_DR = W25X_WRITE_ENABLE;

		__asm("nop");
		__asm("nop");
		__asm("nop");

		while ((SSIx->SSI_SR & SR_TFE) == 0x00)
		{
			;
		}
		while (SSIx->SSI_SR & SR_BUSY)
		{
			;
		}
		while (SSIx->SSI_SR & SR_RFNE)
		{
			temp = SSIx->SSI_DR; // empty fifo
		}
		W25QXX_Wait_Busy();
	}
}

/********************************************************************************
* Function Name	: W25QXX_Write_EnableVolatile
* Description  	: Write Enable for Volatile Status Register.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_Write_EnableVolatile(void)
{
	volatile uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	SSI1_STD_Init(SSI1);

	SSIx->SSI_DR = W25X_WRITE_ENABLE_VSR;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	W25QXX_Wait_Busy();
}

/********************************************************************************
* Function Name	: W25QXX_Write_Disable
* Description  	: Write Disable(set WEL to 0).
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_Write_Disable(void)
{
	volatile uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	if ((W25QXX_ReadSR(W25X_READ_SR1) & 0x02) == 0x02)
	{
		SSIx->SSI_DR = W25X_WRITE_DISABLE;

		__asm("nop");
		__asm("nop");
		__asm("nop");

		while ((SSIx->SSI_SR & SR_TFE) == 0x00)
		{
			;
		}
		while (SSIx->SSI_SR & SR_BUSY)
		{
			;
		}
		while (SSIx->SSI_SR & SR_RFNE)
		{
			temp = SSIx->SSI_DR; // empty fifo
		}

		W25QXX_Wait_Busy();
	}
}

/********************************************************************************
* Function Name	: W25QXX_4ByteAddress_Mode
* Description  	: Enter 4-Byte Address Mode.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_4ByteAddress_Mode(void)
{
	volatile uint8_t status;
	uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	temp = 0x00;
	SSIx->SSI_DR = W25X_ENTER_4BYTE_ADDR;

	__asm("nop");
	__asm("nop");
	__asm("nop");
	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	temp = SSIx->SSI_DR; // empty fifo
}

/********************************************************************************
* Function Name	: W25QXX_QE_Enable
* Description  	: Enables Quad SPI operation(set QE to 1).
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_QE_Enable(void)
{
	uint8_t temp = 0;

	// exspiflash quad mode
	temp = W25QXX_ReadSR(W25X_READ_SR2); // SET QE
	if (!(temp & 0x02))
	{
		W25QXX_Write_EnableVolatile(); // W25QXX_Write_Enable();

		W25QXX_WriteSR(W25X_WRITE_SR2, temp | 0x02);

		W25QXX_Wait_Busy();
	}
	// printf("W25QXX_ReadSR =0x%x  \r\n",W25QXX_ReadSR(W25X_READ_SR2));
}

/********************************************************************************
* Function Name	: W25QXX_ReadID
* Description  	: Read W25qxx ID.
* Input        	: None
* Output       	: None
* Return       	: Device_ID
*********************************************************************************/
uint32_t W25QXX_ReadID(void)
{
	int32_t i;
	volatile uint16_t Device_ID;
	SSI_TypeDef *SSIx = SSI1;

	SSIx->SSI_DR = W25X_MANUFACTDEVICE_ID;
	SSIx->SSI_DR = 0x00;
	SSIx->SSI_DR = 0x00;
	SSIx->SSI_DR = 0x00;

	SSIx->SSI_DR = DUMMY_BYTE;
	SSIx->SSI_DR = DUMMY_BYTE;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	for (i = 0; i < 4; i++)
	{
		Device_ID = SSIx->SSI_DR;
	}

	Device_ID = (SSIx->SSI_DR << 8);
	Device_ID |= (SSIx->SSI_DR);

	return Device_ID;
}

void PY_STD_UIDRead(uint8_t *buf, uint16_t num)
{
	volatile uint8_t temp;
	int32_t i = 0, j = 0;
	int32_t txnum;
	SSI_TypeDef *SSIx = SSI1;

	txnum = num;

	SSIx->SSI_DR = 0x4B;
	SSIx->SSI_DR = DUMMY_BYTE;
	SSIx->SSI_DR = DUMMY_BYTE;
	SSIx->SSI_DR = DUMMY_BYTE;
	SSIx->SSI_DR = DUMMY_BYTE;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while (num > 0)
	{
		if ((SSIx->SSI_SR & SR_TFNF) && (txnum > 0))
		{
			SSIx->SSI_DR = DUMMY_BYTE;
			txnum--;
		}

		if (SSIx->SSI_SR & SR_RFNE)
		{
			if (j < 5)
			{
				temp = SSIx->SSI_DR;
				j++;
			}
			else
			{
				*(buf + i) = SSIx->SSI_DR;
				i++;
				num--;
			}
		}
	}

	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
}

void PY_STD_OTPRead(uint8_t *buf, uint16_t num)
{
	volatile uint8_t temp;
	int32_t i = 0, j = 0;
	int32_t txnum;
	SSI_TypeDef *SSIx = SSI1;

	txnum = num;

	SSIx->SSI_DR = 0x48;
	SSIx->SSI_DR = 0x00;
	SSIx->SSI_DR = 0x10;
	SSIx->SSI_DR = 0x00;
	SSIx->SSI_DR = DUMMY_BYTE;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while (num > 0)
	{
		if ((SSIx->SSI_SR & SR_TFNF) && (txnum > 0))
		{
			SSIx->SSI_DR = DUMMY_BYTE;
			txnum--;
		}

		if (SSIx->SSI_SR & SR_RFNE)
		{
			if (j < 5)
			{
				temp = SSIx->SSI_DR;
				j++;
			}
			else
			{
				*(buf + i) = SSIx->SSI_DR;
				i++;
				num--;
			}
		}
	}

	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
}

//------------------------------------------------------------------ Erase --------------------------------------------------------------------------
/********************************************************************************
* Function Name	: W25QXX_SectorErase4K
* Description  	: Erase a sector (4*1024 bytes).
* Input        	: - addr:sector address
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_SectorErase4K(uint32_t addr)
{
	volatile uint8_t status;
	uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	W25QXX_Write_Enable(); // set WEL

	SSIx->SSI_DR = W25X_SECTOR_ERASE;
	if (Flash_Type == 1)
		SSIx->SSI_DR = (addr >> 24) & 0xFF;
	SSIx->SSI_DR = (addr >> 16) & 0xFF;
	SSIx->SSI_DR = (addr >> 8) & 0xFF;
	SSIx->SSI_DR = (addr)&0xFF;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	W25QXX_Wait_Busy();
}

/********************************************************************************
* Function Name	: W25QXX_BlockErase32KB
* Description  	: Erase a block (32*1024 bytes).
* Input        	: - addr:block address
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_BlockErase32KB(uint32_t addr)
{
	volatile uint8_t status;
	uint8_t temp;
	SSI_TypeDef *SSIx = SSI1;

	// addr=addr*65536;
	temp = 0x00;

	W25QXX_Write_Enable(); // set WEL

	SSIx->SSI_DR = W25X_32KBLOCK_ERASE;
	if (Flash_Type == 1)
		SSIx->SSI_DR = (addr >> 24) & 0xFF;
	SSIx->SSI_DR = (addr >> 16) & 0xFF;
	SSIx->SSI_DR = (addr >> 8) & 0xFF;
	SSIx->SSI_DR = addr & 0xFF;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	W25QXX_Wait_Busy();
}

/********************************************************************************
* Function Name	: W25QXX_BlockErase64KB
* Description  	: Erase a block (64*1024 bytes).
* Input        	: - addr:block address
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_BlockErase64KB(uint32_t addr)
{
	uint8_t temp = 0;

	SSI_TypeDef *SSIx = SSI1;

	W25QXX_Write_Enable(); // set WEL

	SSIx->SSI_DR = W25X_64KBLOCK_ERASE;
	if (Flash_Type == 1)
		SSIx->SSI_DR = (addr >> 24) & 0xFF;
	SSIx->SSI_DR = (addr >> 16) & 0xFF;
	SSIx->SSI_DR = (addr >> 8) & 0xFF;
	SSIx->SSI_DR = (addr)&0xFF;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	W25QXX_Wait_Busy();
}

/********************************************************************************
* Function Name	: W25QXX_FullChipErase
* Description  	: Full Erase.
* Input        	: None
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_FullChipErase(void)
{
	volatile uint8_t status;
	uint8_t temp = 0;
	SSI_TypeDef *SSIx = SSI1;

	W25QXX_Write_Enable(); // set WEL

	SSIx->SSI_DR = W25X_CHIP_ERASE;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR; // empty fifo
	}

	W25QXX_Wait_Busy();
}

// W25QXX_Init
void W25QXX_Init(void)
{
	Flash_ID = W25QXX_ReadID();
	//debug
//	printf("Flash_ID==0x%x  \r\n", Flash_ID);

	if (Flash_ID == 0xEF18 || Flash_ID == 0xC818 || Flash_ID == 0x0B18) // 256M flash
	{
		Flash_Type = 1;
		W25QXX_4ByteAddress_Mode();
	}

	W25QXX_QE_Enable(); // QE
}

//------------------------------------------------------------------ Read --------------------------------------------------------------------------
/********************************************************************************
* Function Name	: W25QXX_STD_Read
* Description  	: Read data of specified length from the specified address.
* Input        	: - buf: buffer for save data
                  - addr: Start address of reading data
                  - num: number bytes to read
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_STD_Read(uint8_t *buf, uint32_t addr, uint16_t num)
{
	volatile uint8_t temp;
	int32_t i = 0, j = 0;
	int32_t txnum;
	SSI_TypeDef *SSIx = SSI1;

	txnum = num;

	SSIx->SSI_DR = W25X_READ;
	if (Flash_Type == 1)
		SSIx->SSI_DR = (addr >> 24) & 0xff;
	SSIx->SSI_DR = (addr >> 16) & 0xff;
	SSIx->SSI_DR = (addr >> 8) & 0xff;
	SSIx->SSI_DR = (addr >> 0) & 0xff;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	if (Flash_Type == 1)
	{
		while (num > 0)
		{
			if ((SSIx->SSI_SR & SR_TFNF) && (txnum > 0))
			{
				SSIx->SSI_DR = DUMMY_BYTE;
				txnum--;
			}

			if (SSIx->SSI_SR & SR_RFNE)
			{
				if (j < 5)
				{
					temp = SSIx->SSI_DR;
					j++;
				}
				else
				{
					*(buf + i) = SSIx->SSI_DR;
					i++;
					num--;
				}
			}
		}
	}
	else
	{
		while (num > 0)
		{
			if ((SSIx->SSI_SR & SR_TFNF) && (txnum > 0))
			{
				SSIx->SSI_DR = DUMMY_BYTE;
				txnum--;
			}

			if (SSIx->SSI_SR & SR_RFNE)
			{
				if (j < 4)
				{
					temp = SSIx->SSI_DR;
					j++;
				}
				else
				{
					*(buf + i) = SSIx->SSI_DR;
					i++;
					num--;
				}
			}
		}
	}

	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
}

/********************************************************************************
* Function Name	: W25QXX_STD_Read_DMA
* Description  	: Read data of specified length from the specified address.
* Input        	: - buf: buffer for save data
                  - addr: Start address of reading data
                  - num: number bytes to read
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_STD_Read_DMA(uint8_t *buf, uint32_t addr, uint16_t num)
{
	SSI_TypeDef *SSIx = SSI1;
	uint8_t addrbyte;

	if (Flash_Type == 1)
		addrbyte = 5;
	else
		addrbyte = 4;

	exFlash_dma_control->DMA_CONFIG = 1;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_SADDR = (uint32_t)&SSIx->SSI_DR;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_DADDR = (uint32_t)buf;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL = DIEC | SNOCHG | P2M_DMA | DWIDTH_B | SWIDTH_B;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL_HIGH = (num + addrbyte); // ��󳤶�Ϊ0x0FFF
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG = (HS_SEL_SRC_HARD);
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG_HIGH = SRC2_SSI1_RX;
	exFlash_dma_control->DMA_CHEN |= (CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));

	SSIx->SSI_DR = W25X_READ;
	if (Flash_Type == 1)
		SSIx->SSI_DR = (addr >> 24) & 0xff;
	SSIx->SSI_DR = (addr >> 16) & 0xff;
	SSIx->SSI_DR = (addr >> 8) & 0xff;
	SSIx->SSI_DR = (addr >> 0) & 0xff;

	SSIx->SSI_DMACR = DMACR_TDMAE;
	while (num)
	{
		if ((SSIx->SSI_SR & SR_TFNF) && (num > 0))
		{
			SSIx->SSI_DR = DUMMY_BYTE;
			num--;
			while (0)
				;
		}
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}

	while (exFlash_dma_control->DMA_CHEN & CHANNEL_STAT(exFlash_CHNUM))
	{
		;
	}
	exFlash_dma_control->DMA_CHEN &= ~(CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));

	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG &= ~(1 << 8);
	SSIx->SSI_DMACR = 0;
}

//------------------------------------------------------------------ Write --------------------------------------------------------------------------
/********************************************************************************
* Function Name	: W25QXX_STD_Write_Page
* Description  	: On a page, write data of specified length at the specified address.
* Input        	: - buf: data start address
                  - addr: Start address of writing data
                  - num: number bytes to write(max one page:256 bytes)
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_STD_Write_Page(uint8_t *buf, uint32_t addr, uint16_t num)
{
	SSI_TypeDef *SSIx = SSI1;
	volatile uint8_t temp;

	SSIx->SSI_DR = W25X_PAGE_PROGRAM;
	if (Flash_Type == 1)
		SSIx->SSI_DR = (addr >> 24) & 0xff;
	SSIx->SSI_DR = (addr >> 16) & 0xff;
	SSIx->SSI_DR = (addr >> 8) & 0xff;
	SSIx->SSI_DR = (addr >> 0) & 0xff;

	__asm("nop");
	__asm("nop");
	__asm("nop");

	while (num > 0)
	{
		if (SSIx->SSI_SR & SR_TFNF)
		{
			SSIx->SSI_DR = *buf;
			buf++;
			num--;
		}
	}
	while ((SSIx->SSI_SR & SR_TFE) == 0x00)
	{
		;
	}
	while (SSIx->SSI_SR & SR_BUSY)
	{
		;
	}
	while (SSIx->SSI_SR & SR_RFNE)
	{
		temp = SSIx->SSI_DR;
	}
	W25QXX_Wait_Busy();
}

/********************************************************************************
* Function Name	: W25QXX_STD_Write_NoCheck
* Description  	: Write SPI FLASH without inspection.
                  You must ensure that all the data written in the address range is 0XFF, otherwise the data written in non-0XFF will fail.
                  With auto-feed function
* Input        	: - buf: data start address
                  - addr: Start address of writing data
                  - num: number bytes to write(max 0xFFFF bytes)
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_STD_Write_NoCheck(uint8_t *buf, uint32_t addr, uint16_t num)
{
	uint16_t pageremain; // The amount of data written to the page each time

	pageremain = 256 - addr % 256; // Number of bytes remaining on a single page
	if (num <= pageremain)
		pageremain = num; // <= 256 bytes
	while (1)
	{
		W25QXX_Write_Enable();
		W25QXX_STD_Write_Page(buf, addr, pageremain);
		SSI1_STD_Init(SSI1);
		W25QXX_Wait_Busy(); // Wait for writing to end
		if (num == pageremain)
			break; // Write finished
		else	   // NumByteToWrite>pageremain
		{
			buf += pageremain;
			addr += pageremain;

			num -= pageremain; // Subtract the number of bytes written
			if (num > 256)
				pageremain = 256; // 256 bytes can be written at a time
			else
				pageremain = num; // Not enough 256 bytes
		}
	}
}

/********************************************************************************
* Function Name	: W25QXX_STD_Write_Page_DMA
* Description  	: On a page, write data of specified length at the specified address.
* Input        	: - buf: data start address
                  - addr: Start address of writing data
                  - num: number bytes to write(max one page:256 bytes)
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_STD_Write_Page_DMA(uint8_t *buf, uint32_t addr, uint16_t num)
{
	SSI_TypeDef *SSIx = SSI1;

	exFlash_dma_control->DMA_CONFIG = 1;
	// Tx
	exFlash_dma_channel[exFlash_CHNUM]->DMA_SADDR = (uint32_t)buf;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_DADDR = (uint32_t)&SSIx->SSI_DR;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL = DNOCHG | SIEC | M2P_DMA | DWIDTH_B | SWIDTH_B | SBSIZE_4 | DBSIZE_4;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL_HIGH = num & 0xFFF; // max 0x0FFF
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG = (HS_SEL_DST_HARD);
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG_HIGH = exFlash_DST_TX;

	// enable dma channel
	exFlash_dma_control->DMA_CHEN |= (CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));

	//	SSI_STD_DMA_Trig(SSIx, PAGE_PROG_CMD, addr, DMACR_TDMAE);

	SSIx->SSI_DR = W25X_PAGE_PROGRAM; // W25X_PageProgram_4B;
	if (Flash_Type == 1)
		SSIx->SSI_DR = (addr >> 24) & 0xff;
	SSIx->SSI_DR = (addr >> 16) & 0xff;
	SSIx->SSI_DR = (addr >> 8) & 0xff;
	SSIx->SSI_DR = (addr >> 0) & 0xff;
	SSIx->SSI_DMACR = DMACR_TDMAE;

	while (exFlash_dma_control->DMA_CHEN & CHANNEL_STAT(exFlash_CHNUM))
	{
		;
	}
	exFlash_dma_control->DMA_CHEN &= ~(CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));

	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG &= ~(1 << 8);
	SSIx->SSI_DMACR = 0;
}

/********************************************************************************
* Function Name	: W25QXX_STD_Write_NoCheck_DMA
* Description  	: Write SPI FLASH without inspection.
                  You must ensure that all the data written in the address range is 0XFF, otherwise the data written in non-0XFF will fail.
                  With auto-feed function
* Input        	: - buf: data start address
                  - addr: Start address of writing data
                  - num: number bytes to write(max 0xFFFF bytes)
* Output       	: None
* Return       	: None
*********************************************************************************/
void W25QXX_STD_Write_NoCheck_DMA(uint8_t *buf, uint32_t addr, uint16_t num)
{
	uint16_t pageremain; // The amount of data written to the page each time

	pageremain = 256 - addr % 256; // Number of bytes remaining on a single page
	if (num <= pageremain)
		pageremain = num; // <= 256 bytes
	while (1)
	{
		W25QXX_Write_Enable();
		W25QXX_STD_Write_Page_DMA(buf, addr, pageremain);
		SSI1_STD_Init(SSI1);
		W25QXX_Wait_Busy(); // Wait for writing to end
		if (num == pageremain)
			break; // Write finished
		else	   // NumByteToWrite>pageremain
		{
			buf += pageremain;
			addr += pageremain;

			num -= pageremain; // Subtract the number of bytes written
			if (num > 256)
				pageremain = 256; // 256 bytes can be written at a time
			else
				pageremain = num; // Not enough 256 bytes
		}
	}
}

//-------------------------------------------------------------DMA-------------------------------------------------------------------------
/*Dual_Read_Mode*/
void NorFlash_Dual_Init(uint8_t command_bits, uint8_t dummy_bits, uint32_t num)
{
	SSI_TypeDef *SSIx = SSI1;
#if 1
	//-----------------DUAL MODE-----------------------
	// while(SSIx->SSI_SR&SR_BUSY){;}
	SSIx->SSI_SSIENR = 0x00;
	SSIx->SSI_BAUDR = QUAD_BAUDR;
	SSIx->SSI_CTRLR1 = ((num == 0) ? 0 : (num - 1));
	SSIx->SSI_CTRLR0 = (CTRLR0_SPI_FRF_DUAL | CTRLR0_FRF_MOT | CTRLR0_DFS_VALUE(0x07) | CTRLR0_TMOD_TX_AND_RX); // DUAL model
	SSIx->SSI_SPI_CTRLR0 = (CTRLR0_TRANS_TYPE_TT0																// CMD,address,dummy model
							| CTRLR0_INST_L_VALUE(command_bits)													// CMD_Len: 0=0bit��1=4bit��2=8bit, 3=16bit
							| CTRLR0_WAIT_CYCLES_VALUE(dummy_bits)												// dummy_clock: max=31
							| CTRLR0_CLK_STRETCH_EN_MASK);														// Clock extension enable(TX_ONLY necessary)
	if (Flash_Type == 1)
		SSIx->SSI_SPI_CTRLR0 |= CTRLR0_ADDR_L_VALUE(0x08); // 4 Byte addr,len= n*4 bit ( max_n=15)
	else
		SSIx->SSI_SPI_CTRLR0 |= CTRLR0_ADDR_L_VALUE(0x06); // 3 Byte addr,len= n*4 bit ( max_n=15)

	SSIx->SSI_TXFTLR = TXFTLR_TFT(0) | TXFTLR_TXFTHR(1);
	SSIx->SSI_RXFTLR = RXFTLR_TFT(7);
	if (QUAD_BAUDR == 0x02 && g_sys_clk >= 80000000) // high speed
	{
		SSIx->SSI_RX_SAMPLE_DELAY = 0x00010001;
	}
	SSIx->SSI_IMR = 0x00;
	SSIx->SSI_DMACR = 0; // dma
	SSIx->SSI_SSIENR = 0x01;
//----------------- END -----------------------------------------------------
#else
//-----------------QUAL MODE-----------------------------------------------------
// none
//----------------- END -----------------------------------------------------
#endif
}

void NorFlash_Read(uint8_t *buf, uint32_t addr, uint32_t num)
{
	int32_t i = 0;
	SSI_TypeDef *SSIx = SSI1;

	// Init
	NorFlash_Dual_Init(2, 8, num);

	// read
	SSIx->SSI_DR = W25X_READ_FAST_DUAL;
	SSIx->SSI_DR = addr;

	__asm("nop");
	__asm("nop");
	__asm("nop");
	while ((SSIx->SSI_SR & SR_TFE) != SR_TFE)
	{
		;
	}

	// while( ((num>0)&&(SSIx->SSI_SR&SR_BUSY))||(SSIx->SSI_SR&SR_RFNE) )
	while (num > 0)
	{
		if (SSIx->SSI_SR & SR_RFNE)
		{
			*(buf + i) = SSIx->SSI_DR;
			i++;
			num--;
		}
	}
}

//------------------------------------------------------------------ DMA --------------------------------------------------------------------------
void NorFlash_DMA_Read(uint8_t *buf, uint32_t addr, uint32_t num)
{
	//-----------------DUAL MODE---------------------------
	SSI_TypeDef *SSIx = SSI1;

	// Init
	NorFlash_Dual_Init(2, 8, num);

	// dma read
	// DMA_REG_Init(DMA2_BASE_ADDR);
	exFlash_dma_control->DMA_CONFIG = 1;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_SADDR = (uint32_t)&SSIx->SSI_DR;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_DADDR = (uint32_t)buf;
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL = DIEC | SNOCHG | P2M_DMA | DBSIZE_1 | SBSIZE_1 | DWIDTH_B | SWIDTH_B; // 1x8 bits
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CTRL_HIGH = num;															// Max: 0x0FFF
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG = (HS_SEL_SRC_HARD) | (HS_SEL_DST_SOFT);								//|(CH_PRIOR1);
	exFlash_dma_channel[exFlash_CHNUM]->DMA_CFG_HIGH = exFlash_SRC_RX;													// DMA_Rx
	// enable dma channel
	exFlash_dma_control->DMA_CHEN |= CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM);

	SSIx->SSI_DR = W25X_READ_FAST_DUAL;
	SSIx->SSI_DR = addr;
	SSIx->SSI_DMACR = DMACR_RDMAE;
	//----------------- END -----------------------------------------------------
}

void NorFlash_DMA_Wait(void)
{
	SSI_TypeDef *SSIx = SSI1;
	while (exFlash_dma_control->DMA_CHEN & CHANNEL_STAT(exFlash_CHNUM))
	{
		;
	}
	exFlash_dma_control->DMA_CHEN &= ~(CHANNEL_WRITE_ENABLE(exFlash_CHNUM) | CHANNEL_ENABLE(exFlash_CHNUM));
	// exFlash_dma_channel[0]->DMA_CFG &= ~(1<<8);
	// exFlash_dma_control->DMA_CONFIG = 0;
	SSIx->SSI_DMACR = 0;
}
